#ifndef TRAINING_LEVEL1_H
#define TRAINING_LEVEL1_H

#include "common_includes.h"

#include "work_result.h"

template<typename T, typename Music_T>
class TrainingLevel1{ //Todo, copy or move?
private:
	thread thread_;
	promise<WorkResult<T>> promise_;
	mutex mutex_;
	condition_variable condition_variable_;bool destruction;

	shared_ptr<mutex> mutex_outside;
	shared_ptr<condition_variable> condition_variable_outside;

	vector<reference_wrapper<Music<Music_T>>> musics;
	const Transitions<T>* transitions;
	Music_T best_state_bias;

	vector<array<T, NUM_STATES>> forward_backward;
	vector<array<T, NUM_STATES>> forward_backward_temp;

public:
	TrainingLevel1(){
		destruction = false;
		thread_ = thread(&TrainingLevel1::working_thread, this);
	}

	~TrainingLevel1(){
		{
			lock_guard<mutex> lock(mutex_);
			destruction = true;
			condition_variable_.notify_one();
		}
		thread_.join();
	}

	TrainingLevel1(const TrainingLevel1& tl1) = delete;

	WorkResult<T> evaluate(const vector<reference_wrapper<Music<Music_T>>>& musics, const Transitions<T>& transitions, Music_T best_state_bias){
		WorkResult<T> work_result;
		work_result.clear();
		for(unsigned i=0; i<musics.size(); ++i)
		{
			Music<Music_T>& music = musics[i];
			music.forward_backward(transitions, forward_backward, forward_backward_temp, best_state_bias);
			vector<State>best_states = music.best_states(forward_backward,music.frame_size);
			vector<State>best_path = music.best_path(transitions);
			vector<reference_wrapper<vector<State>>> test;
//			cout << music.ground_truth.size() << endl;
//			cout << best_path.size() << endl;
//			cout << best_states.size() << endl;
//			cout << forward_backward.size() << endl;
//			cout << music.frame_size << endl;
			if (music.ground_truth[0] == -1)
			{
				cout << music.file_name << endl;
			}
			vector<T> scores = Music<Music_T>::score_framewise(forward_backward, music.frame_size,{music.ground_truth, best_path, best_states});

			work_result.score_ground_truth += scores[0];
			work_result.score_best_path += (decltype(work_result.score_best_path))scores[1];
			work_result.score_best_states += (decltype(work_result.score_best_states))scores[2];
			work_result.similarity_bp_gt += Music<Music_T>::path_similarity(best_path, music.ground_truth);
			work_result.similarity_bs_gt += Music<Music_T>::path_similarity(best_states, music.ground_truth);
			work_result.similarity_bp_bs += Music<Music_T>::path_similarity(best_path, best_states);
		}
		return work_result;
	}

	future<WorkResult<T>> async_evaluate(vector<reference_wrapper<Music<Music_T>>> musics,
			const Transitions<T>& transitions, Music_T best_state_bias,
			shared_ptr<mutex> mutex_outside = shared_ptr<mutex>(new mutex()),
			shared_ptr<condition_variable> condition_variable_outside = shared_ptr<condition_variable>(new condition_variable())){ // TODO const for the first argument?
		lock_guard<std::mutex> lock(mutex_);
		promise_ = promise<WorkResult<T>>();

		this->musics = musics;
		this->transitions = &transitions;
		this->best_state_bias = best_state_bias;
		this->mutex_outside = mutex_outside;
		this->condition_variable_outside = condition_variable_outside;

		condition_variable_.notify_one();
		return promise_.get_future();
	}

	void working_thread(){
		unique_lock<mutex> lock(mutex_);
		while (true){
			condition_variable_.wait(lock);
			if (destruction){
				break;
			}

			WorkResult<T>&& work_result = evaluate(musics, *transitions, best_state_bias);
			lock_guard<mutex> lock_outside(*mutex_outside);
			promise_.set_value(work_result);
			condition_variable_outside->notify_one();
		}
	}
};
#endif

